using System;
using System.Collections.Generic;
using System.Text;
using System.Runtime.InteropServices;
using System.Diagnostics;
using System.ComponentModel;

namespace TelnetExpect.Ntlm
{
    public class MySspi
    {
        const string m_acPrincipal = null;
        string m_iscTarget;
        //
        const int cbMaxMessage = 12000;
        const ISC_REQ MessageAttribute = ISC_REQ.INTEGRITY;
        //
        SecHandle m_hCred = new SecHandle();
        SECURITY_INTEGER lifetime = new SECURITY_INTEGER();
        //
        SecHandle m_secCtx = new SecHandle();
        SecBufferDescHolder m_outBuffDesc = new SecBufferDescHolder(cbMaxMessage);

        internal MySspi()
        {
        }

        /// <summary>
        /// Initialize an instance of the <see cref="MySspi"/> class.
        /// </summary>
        /// <param name="target">The SPN (Service Principle Name) of the target
        /// service, e.g. "telnet/pc1".
        /// <see cref="M:MySspi.CreateSpn"/> can be used to create the SPN string.
        /// </param>
        internal MySspi(string target)
            : this()
        {
            Trace.WriteLine(string.Format(System.Globalization.CultureInfo.InvariantCulture,
                "Given SPN is: \"{0}\".", target));
            m_iscTarget = target;
        }

        //------
        /// <summary>
        /// 
        /// </summary>
        /// <param name="serviceClass">A <see cref="T:System.String"/> that specifies
        /// the class of the service. This parameter can be any string unique to
        /// that service; either the protocol name, for example, ldap, or the
        /// string form of a GUID are acceptable.
        ///</param>
        /// <param name="hostName">A <see cref="T:System.String"/> that specifies
        /// the DNS name, NetBIOS name, or distinguished name (DN). This parameter
        /// must be supplied.
        /// </param>
        /// <param name="port">Port number for an instance of the service. Use 0
        /// for the default port. If this parameter is zero, the SPN does not
        /// include a port number.  That is also the case if
        /// <paramref name="port"/>==<paramref name="defaultPort"/>.
        /// </param>
        /// <param name="defaultPort">The default Port number for the service, for
        /// instance 23 for Telnet, and XX for LDAP.  If
        /// <paramref name="port"/>==<paramref name="defaultPort"/>, then the SPN
        /// does not include a port number.
        /// </param>
        /// <returns>A <see cref="T:System.String"/> containing the constructed SPN.
        /// </returns>
        public static string CreateSpn(string serviceClass, string hostName, int port, int defaultPort)
        {
            /*
            InstanceName___
            [in, optional] Pointer to a constant null-terminated string that specifies
                the DNS name or IP address of the host for an instance of the service. 
            If ServiceName specifies the DNS or NetBIOS name of the service host
                computer, the InstanceName parameter must be NULL.
            If ServiceName specifies a DNS domain name, the name of a DNS SRV record,
                or a distinguished name, such as the DN of a service connection
                point, the InstanceName parameter must specify the DNS or NetBIOS
                name of the service host computer.
            Referrer___
            [in, optional] Pointer to a constant null-terminated string that specifies
                the DNS name of the host that gave an IP address referral. This
                parameter is ignored unless the ServiceName parameter specifies
                an IP address.
            */
            if (string.IsNullOrEmpty(serviceClass))
                throw new ArgumentNullException("serviceClass");
            if (string.IsNullOrEmpty(hostName))
                throw new ArgumentNullException("hostName");
            string spn = serviceClass + "/" + hostName;
            if (port != 0 && port != defaultPort) {
                spn += ":" + port;
            }
            return spn;
        }

        //------
        /// <summary>
        /// Handle the next phase of authentication processing.
        /// </summary>
        /// <param name="input">Optional byte array containing authentication token 
        /// from the peer.  For NTLM should be null when called the first time.</param>
        /// <param name="done">Output boolean signalling whether the authentication
        /// process has completed.
        /// </param>
        /// <returns>Bytte array of authentication token to send to the peer.
        /// </returns>
        internal byte[] DoStep(byte[] input, out bool done)
        {
            Int32 ret;
            //
            ret = NativeMethods.AcquireCredentialsHandleW(m_acPrincipal, PackageName_Negotiate,
                SECPKG_CRED.OUTBOUND,
                null, null, IntPtr.Zero, null, out m_hCred, out lifetime);
            if (ret != 0)
                throw new Win32Exception(ret);
            //
            int ss0;
            SECURITY_STATUS ss;
            ISC_RET outFlags;
            if (input == null) {
                ss0 = NativeMethods.InitializeSecurityContext0(
                    ref m_hCred,
                    IntPtr.Zero,
                    m_iscTarget,
                    MessageAttribute,
                    0,
                    Data_Representation_Constant.SECURITY_NATIVE_DREP,
                    IntPtr.Zero,
                    0,
                    out m_secCtx,
                    m_outBuffDesc.Alloc(),
                    out outFlags,
                    out lifetime);
                m_outBuffDesc.Release();
            } else {
                SecBufferDescHolder inBuf = new SecBufferDescHolder(input);
                ss0 = NativeMethods.InitializeSecurityContext1(
                        ref m_hCred,
                        ref m_secCtx,
                        m_iscTarget,
                        MessageAttribute,
                        0,
                        Data_Representation_Constant.SECURITY_NATIVE_DREP,
                        inBuf.Alloc(),
                        0,
                        out m_secCtx,
                        m_outBuffDesc.Alloc(),
                        out outFlags,
                        out lifetime);
                inBuf.Release();
                m_outBuffDesc.Release();
            }
            ss = (SECURITY_STATUS)ss0;
            bool success = SEC_SUCCESS(ss);
            if (!success)
                throw new Win32Exception(ss0);
            if ((SECURITY_STATUS.SEC_I_COMPLETE_NEEDED == ss)
                    || (SECURITY_STATUS.SEC_I_COMPLETE_AND_CONTINUE == ss)) {
                throw new NotImplementedException("Don't expect to have to call CompleteAuthToken on client side, and not for NTLM neither.");
            }
            int size = m_outBuffDesc.Size;
            done = !((SECURITY_STATUS.SEC_I_CONTINUE_NEEDED == ss) ||
                (SECURITY_STATUS.SEC_I_COMPLETE_AND_CONTINUE == ss));
            Console.Error.WriteLine("Token buffer generated ({0} bytes):", size);
            PrintHexDump(m_outBuffDesc.Buffer, size);
            Debug.Assert(done == (0 == (outFlags & ISC_RET.INTERMEDIATE_RETURN)), "done == !IntermediateReturn");
            if (0 == (outFlags & ISC_RET.INTERMEDIATE_RETURN)) {
                Console.Error.WriteLine("SSPI result flags: {0}", outFlags);
                DumpInfo(ref m_secCtx);
            }
            //
            byte[] result = m_outBuffDesc.ToArray();
            return result;
        }

        private void DumpInfo(ref SecHandle context)
        {
            SecPkgAttr.SecPkgContext_NegotiationInfoW negInfo = new SecPkgAttr.SecPkgContext_NegotiationInfoW();
            int ss0;
            ss0 = NativeMethods.QueryContextAttributesW(ref context,
                SecPkgAttr.Id.NegotiationInfo, ref negInfo);
            PrintErrorIf(ss0, "NegotiationInfo");
            if (SEC_SUCCESS(ss0)) {
                IntPtr p = negInfo.PackageInfo;
                SecPkgAttr.SecPkgInfoW pkgInfo = (SecPkgAttr.SecPkgInfoW)
                    Marshal.PtrToStructure(p, typeof(SecPkgAttr.SecPkgInfoW));
                TraceWriteLine("NegState: {0} = 0x{0:X}", negInfo.NegotiationState);
                TraceWriteLine("Pkg Name: \"{0}\", Comment: \"{1}\"",
                    pkgInfo.Name, pkgInfo.Comment);
                TraceWriteLine("    Capabilities: [{0}] = 0x{0:X}",
                    pkgInfo.fCapabilities);
            }
            SecPkgAttr.SecPkgContext_Sizes sizes = new SecPkgAttr.SecPkgContext_Sizes();
            ss0 = NativeMethods.QueryContextAttributesW(ref context,
                SecPkgAttr.Id.Sizes, ref sizes);
            PrintErrorIf(ss0, "Sizes");
            if (SEC_SUCCESS(ss0)) {
                TraceWriteLine("Sizes: Token {0}, Signature: {1}, Block: {2}, Trailer: {3}",
                    sizes.cbMaxToken, sizes.cbMaxSignature, sizes.cbBlockSize, sizes.cbSecurityTrailer);
            }
            SecPkgAttr.SecPkgContext_KeyInfoW keyInfo = new SecPkgAttr.SecPkgContext_KeyInfoW();
            ss0 = NativeMethods.QueryContextAttributesW(ref context,
                SecPkgAttr.Id.KeyInfo, ref keyInfo);
            PrintErrorIf(ss0, "KeyInfo");
            if (SEC_SUCCESS(ss0)) {
                TraceWriteLine("KeyInfo: SignatureAlg: {0}, EncryptAlg: {1}, KeyLen: {2}",
                    keyInfo.sSignatureAlgorithmName, keyInfo.sEncryptAlgorithmName, keyInfo.KeySize);
            }
        }

        private void TraceWriteLine(string format, params object[] args)
        {
            Trace.WriteLine(string.Format(System.Globalization.CultureInfo.InvariantCulture,
                format, args));
        }

        private void PrintErrorIf(int ss0, string name)
        {
            SECURITY_STATUS ss = (SECURITY_STATUS)ss0;
            bool success = SEC_SUCCESS(ss);
            if (!success) {
                Win32Exception ex = new Win32Exception(ss0);
                Trace.WriteLine("Error at '" + name + "' is: (0x" + ss0.ToString("X8") + "), " + ex.Message);
            }
        }

        private static void PrintHexDump(byte[] buf, int count)
        {
            string txt = BitConverter.ToString(buf, 0, count);
            Console.Error.WriteLine(txt);
        }

        const string PackageName_Negotiate = "Negotiate";

        static class NativeMethods
        {
            [DllImport("Secur32.dll", CharSet = CharSet.Unicode)]
            public static extern Int32 AcquireCredentialsHandleW(
                string /*SEC_WCHAR SEC_FAR *-*/ pszPrincipal,   // Name of principal
                string /*SEC_WCHAR SEC_FAR *-*/ pszPackage,     // Name of package
                SECPKG_CRED /*unsigned long*/ fCredentialUse,       // Flags indicating use
                byte[] /*void SEC_FAR *-*/ pvLogonId,           // Pointer to logon ID
                byte[] /*void SEC_FAR *-*/ pAuthData,           // Package specific data
                IntPtr /*SEC_GET_KEY_FN*/ pGetKeyFn,           // Pointer to GetKey() func
                byte[] /*void SEC_FAR *-*/ pvGetKeyArgument,    // Value to pass to GetKey()
                out SecHandle /*PCredHandle*/ phCredential,           // (out) Cred Handle
                out SECURITY_INTEGER /*PTimeStamp*/ ptsExpiry                // (out) Lifetime (optional)
            );

            [DllImport("Secur32.dll", EntryPoint = "InitializeSecurityContextW", CharSet = CharSet.Unicode)]
            public static extern Int32 InitializeSecurityContext0(
                ref SecHandle /*PCredHandle*/ phCredential,        // Cred to base context
                IntPtr /*ref SecHandle*/ /*PCtxtHandle*/ NULL_PTR_phContext,           // Existing context (OPT)
                string /*SEC_WCHAR SEC_FAR * */ pszTargetName,     // Name of target
                ISC_REQ /*unsigned long*/ fContextReq,               // Context Requirements
                Int32 /*unsigned long*/ Reserved1,                 // Reserved, MBZ
                Data_Representation_Constant /*unsigned long*/ TargetDataRep,             // Data rep of target
                IntPtr /*PSecBufferDesc*/ pInput,                  // Input Buffers
                Int32 /*unsigned long*/ Reserved2,                 // Reserved, MBZ
                out SecHandle /*PCtxtHandle*/ phNewContext,               // (out) New Context handle
                IntPtr /*PSecBufferDesc*/ pOutput,                 // (inout) Output Buffers
                out ISC_RET /*unsigned long SEC_FAR * */ pfContextAttr,  // (out) Context attrs
                out SECURITY_INTEGER /*PTimeStamp*/ ptsExpiry                    // (out) Life span (OPT)
                );

            [DllImport("Secur32.dll", EntryPoint = "InitializeSecurityContextW", CharSet = CharSet.Unicode)]
            public static extern Int32 InitializeSecurityContext1(
                ref SecHandle /*PCredHandle*/ phCredential,        // Cred to base context
                ref SecHandle /*PCtxtHandle*/ phContext,           // Existing context (OPT)
                string /*SEC_WCHAR SEC_FAR * */ pszTargetName,     // Name of target
                ISC_REQ /*unsigned long*/ fContextReq,               // Context Requirements
                Int32 /*unsigned long*/ Reserved1,                 // Reserved, MBZ
                Data_Representation_Constant /*unsigned long*/ TargetDataRep,             // Data rep of target
                IntPtr /*PSecBufferDesc*/ pInput,                  // Input Buffers
                Int32 /*unsigned long*/ Reserved2,                 // Reserved, MBZ
                out SecHandle /*PCtxtHandle*/ phNewContext,               // (out) New Context handle
                IntPtr /*PSecBufferDesc*/ pOutput,                 // (inout) Output Buffers
                out ISC_RET /*unsigned long SEC_FAR * */ pfContextAttr,  // (out) Context attrs
                out SECURITY_INTEGER /*PTimeStamp*/ ptsExpiry                    // (out) Life span (OPT)
                );

            //----
            [DllImport("Secur32.dll", CharSet = CharSet.Unicode)]
            public static extern Int32/*SECURITY_STATUS*/ QueryContextAttributesW(
                ref SecHandle /*PCtxtHandle*/ phContext,
                SecPkgAttr.Id/*ULONG*/ ulAttribute,
                IntPtr pBuffer
                );
            [DllImport("Secur32.dll", CharSet = CharSet.Unicode)]
            public static extern Int32/*SECURITY_STATUS*/ QueryContextAttributesW(
                ref SecHandle /*PCtxtHandle*/ phContext,
                SecPkgAttr.Id/*ULONG*/ ulAttribute,
                ref SecPkgAttr.SecPkgContext_NegotiationInfoW pBuffer
                );
            [DllImport("Secur32.dll", CharSet = CharSet.Unicode)]
            public static extern Int32/*SECURITY_STATUS*/ QueryContextAttributesW(
                ref SecHandle /*PCtxtHandle*/ phContext,
                SecPkgAttr.Id/*ULONG*/ ulAttribute,
                ref SecPkgAttr.SecPkgContext_Sizes pBuffer
                );
            [DllImport("Secur32.dll", CharSet = CharSet.Unicode)]
            public static extern Int32/*SECURITY_STATUS*/ QueryContextAttributesW(
                ref SecHandle /*PCtxtHandle*/ phContext,
                SecPkgAttr.Id/*ULONG*/ ulAttribute,
                ref SecPkgAttr.SecPkgContext_KeyInfoW pBuffer
                );

        }//class2

        static bool SEC_SUCCESS(int status)
        {
            return ((status) >= 0);
        }

        static bool SEC_SUCCESS(SECURITY_STATUS status)
        {
            return ((status) >= 0);
        }

        /// <summary>
        /// A flag that indicates how these credentials will be used. 
        /// http://msdn.microsoft.com/en-us/library/aa374713(v=vs.85).aspx
        /// </summary>
        enum SECPKG_CRED : uint
        {
            /// <summary>
            /// Validate an incoming server credential.
            /// </summary>
            /// <remarks>
            /// <para>Inbound credentials might be validated by using an 
            /// authenticating authority when InitializeSecurityContext (Kerberos) 
            /// or AcceptSecurityContext (Kerberos) is called. If such an 
            /// authority is not available, the function will fail and return 
            /// SEC_E_NO_AUTHENTICATING_AUTHORITY. Validation is package specific.
            /// </para>
            /// </remarks>
            INBOUND = 0x00000001,
            /// <summary>
            /// Allow a local client credential to prepare an outgoing token.
            /// </summary>
            OUTBOUND = 0x00000002,
            /// <summary>
            /// Validate an incoming credential or use a local credential to 
            /// prepare an outgoing token. This flag enables both other flags.
            /// </summary>
            BOTH = 0x00000003,
            DEFAULT = 0x00000004,
            RESERVED = 0xF0000000,

            //----

            /// <summary>
            /// The security does not use default logon credentials or credentials from Credential Manager.
            /// </summary>
            /// -
            /// <remarks>
            /// <note>Windows Server 2008, Windows Vista, Windows Server 2003, 
            /// Windows XP, and Windows 2000:  This value is not supported.
            /// </note>
            /// <para>Header File:
            /// </para>
            /// <para>SSP SHOULD prompt the user for credentials/consent, independent
            /// of whether credentials to be used are the 'logged on' credentials
            /// or retrieved from credman.
            /// </para> 
            /// <para>An SSP may choose not to prompt, however, in circumstances determined
            /// by the SSP.
            /// </para> 
            /// </remarks>
            _notUsed_AUTOLOGON_RESTRICTED = 0x00000010,

            /// <summary>
            /// The function processes server policy and returns 
            /// SEC_E_NO_CREDENTIALS, indicating that the application should 
            /// prompt for credentials.
            /// </summary>
            /// -
            /// <remarks>
            /// <note>Windows Server 2008, Windows Vista, Windows Server 2003, 
            /// Windows XP, and Windows 2000:  This value is not supported.
            /// </note>
            /// <para>Header File:
            /// </para>
            /// <para>auth will always fail, ISC() is called to process policy data only
            /// </para> 
            /// </remarks>
            _notUsed_PROCESS_POLICY_ONLY = 0x00000020,
        }

        /// <summary>
        /// InitializeSecurityContext Requirement (and return flags).
        /// </summary>
        [Flags]
        enum ISC_REQ
        {
            DELEGATE = 0x00000001,
            MUTUAL_AUTH = 0x00000002,
            REPLAY_DETECT = 0x00000004,
            SEQUENCE_DETECT = 0x00000008,
            CONFIDENTIALITY = 0x00000010,
            USE_SESSION_KEY = 0x00000020,
            PROMPT_FOR_CREDS = 0x00000040,
            USE_SUPPLIED_CREDS = 0x00000080,
            ALLOCATE_MEMORY = 0x00000100,
            USE_DCE_STYLE = 0x00000200,
            DATAGRAM = 0x00000400,
            CONNECTION = 0x00000800,
            CALL_LEVEL = 0x00001000,
            FRAGMENT_SUPPLIED = 0x00002000,
            EXTENDED_ERROR = 0x00004000,
            STREAM = 0x00008000,
            INTEGRITY = 0x00010000,
            IDENTIFY = 0x00020000,
            NULL_SESSION = 0x00040000,
            MANUAL_CRED_VALIDATION = 0x00080000,
            RESERVED1 = 0x00100000,
            FRAGMENT_TO_FIT = 0x00200000
        }

        /// <summary>
        /// InitializeSecurityContext (Requirement and) return flags.
        /// </summary>
        [Flags]
        enum ISC_RET
        {
            DELEGATE = 0x00000001,
            MUTUAL_AUTH = 0x00000002,
            REPLAY_DETECT = 0x00000004,
            SEQUENCE_DETECT = 0x00000008,
            CONFIDENTIALITY = 0x00000010,
            USE_SESSION_KEY = 0x00000020,
            USED_COLLECTED_CREDS = 0x00000040,
            USED_SUPPLIED_CREDS = 0x00000080,
            ALLOCATE_MEMORY = 0x00000100,
            USED_DCE_STYLE = 0x00000200,
            DATAGRAM = 0x00000400,
            CONNECTION = 0x00000800,
            INTERMEDIATE_RETURN = 0x00001000,
            CALL_LEVEL = 0x00002000,
            EXTENDED_ERROR = 0x00004000,
            STREAM = 0x00008000,
            INTEGRITY = 0x00010000,
            IDENTIFY = 0x00020000,
            NULL_SESSION = 0x00040000,
            MANUAL_CRED_VALIDATION = 0x00080000,
            RESERVED1 = 0x00100000,
            FRAGMENT_ONLY = 0x00200000
        }

        enum Data_Representation_Constant
        {
            SECURITY_NATIVE_DREP = 0x00000010,
            SECURITY_NETWORK_DREP = 0x00000000
        }


        struct SecHandle // CredHandle, CtxtHandle
        {
            public IntPtr dwLower;
            public IntPtr dwUpper;
        }

        struct SecBufferDesc
        {
            public Int32 /*unsigned long*/ ulVersion;            // Version number
            public Int32 /*unsigned long*/ cBuffers;             // Number of buffers
            public IntPtr /*PSecBuffer*/ pBuffers;                // Pointer to array of buffers

            public SecBufferDesc(byte[] buffer)
            {
                ulVersion = 0;
                cBuffers = 1;
                SecBuffer tmp = new SecBuffer(buffer);
                IntPtr mem = Marshal.AllocHGlobal(Marshal.SizeOf(tmp));
                Marshal.StructureToPtr(tmp, mem, false);
                pBuffers = mem;
            }

            public int Size
            {
                get
                {
                    if (cBuffers != 1)
                        throw new InvalidOperationException("Only support one buffer.");
                    object tmp0 = Marshal.PtrToStructure(pBuffers, typeof(SecBuffer));
                    SecBuffer tmp = (SecBuffer)tmp0;
                    int size = tmp.cbBuffer;
                    return size;
                }
            }
        }

        class SecBufferDescHolder
        {
            const int Offset_SecDesc = 0;
            const int Offset_SecDesc_Ptr = 8;
            const int Sizeof_SecDesc = 2 * 4 + 8; //allow for x64 ptr.
            const int Offset_SecBuff = Sizeof_SecDesc;
            const int Offset_SecBuff_Len = Sizeof_SecDesc + 0;
            const int Offset_SecBuff_Ptr = Sizeof_SecDesc + 8;
            const int Sizeof_SecBuff = 2 * 4 + 8; //allow for x64 ptr.
            const int Offset_Buffer = Sizeof_SecDesc + Sizeof_SecBuff;
            //
            readonly byte[] m_buf;
            readonly int m_dataCapacity;
            IntPtr m_alloced;
            int m_size;

            public SecBufferDescHolder(int size)
            {
                m_dataCapacity = size;
                m_buf = new byte[size];
            }

            public SecBufferDescHolder(byte[] src)
            {
                m_dataCapacity = src.Length;
                m_buf = src;
            }

            public IntPtr Alloc()
            {
                if (m_alloced != IntPtr.Zero)
                    throw new InvalidOperationException();
                //
                Debug.Assert(m_buf.Length == m_dataCapacity, "user broke buffer?");
                Debug.Assert(m_buf.Length > 0, "unsigned wrap!");
                //
                const int s0 = Sizeof_SecDesc;
                Debug.Assert(s0 >= Marshal.SizeOf(typeof(SecBufferDesc)));
                const int s1 = Sizeof_SecBuff;
                Debug.Assert(s1 >= Marshal.SizeOf(typeof(SecBufferS)));
                IntPtr p = Marshal.AllocHGlobal(m_dataCapacity + s0 + s1);
                Marshal.Copy(m_buf, 0, new IntPtr(p.ToInt64() + s0 + s1), m_buf.Length);
                //
                byte[] structs = new byte[s0 + s1];
                // Desc
                const int Version = 0;
                const int NumBufs = 1;
                BitConverter.GetBytes(Version).CopyTo(structs, Offset_SecDesc + 0);
                BitConverter.GetBytes(NumBufs).CopyTo(structs, Offset_SecDesc + 4);
                // Buff
                BitConverter.GetBytes(m_buf.Length).CopyTo(structs, Offset_SecBuff + 0);
                const int BufType = 2;
                BitConverter.GetBytes(BufType).CopyTo(structs, Offset_SecBuff + 4);
                //
                Marshal.Copy(structs, 0, p, structs.Length);
                //
                // Fix Pointers
                // pSecDesc.pBuffers = pSecBuff;
                IntPtr pDst = new IntPtr(p.ToInt64() + Offset_SecDesc_Ptr);
                IntPtr pObj = new IntPtr(p.ToInt64() + Offset_SecBuff);
                Marshal.WriteIntPtr(pDst, pObj);
                // pSecBuff.pvBuffer = pData;
                BitConverter.GetBytes(m_buf.Length).CopyTo(m_buf, Offset_SecBuff + 0);
                pDst = new IntPtr(p.ToInt64() + Offset_SecBuff_Ptr);
                pObj = new IntPtr(p.ToInt64() + Offset_Buffer);
                Marshal.WriteIntPtr(pDst, pObj);
                //
                m_alloced = p;
                return p;
            }

            public void Release()
            {
                if (m_alloced == IntPtr.Zero)
                    throw new InvalidOperationException();
                const int s0 = Sizeof_SecDesc;
                Debug.Assert(s0 >= Marshal.SizeOf(typeof(SecBufferDesc)));
                const int s1 = Sizeof_SecBuff;
                Debug.Assert(s1 >= Marshal.SizeOf(typeof(SecBufferS)));
                Marshal.Copy(new IntPtr(m_alloced.ToInt64() + s0 + s1), m_buf, 0, m_buf.Length);
                //
                int size = Marshal.ReadInt32(m_alloced, Offset_SecBuff_Len);
                Debug.Assert(size > 0, "unsigned wrap!");
                m_size = size;
                m_alloced = IntPtr.Zero;
            }

            public int Size
            {
                get
                {
                    if (m_alloced != IntPtr.Zero)
                        throw new InvalidOperationException();
                    return m_size;
                }
            }

            public byte[] Buffer
            {
                get
                {
                    if (m_alloced != IntPtr.Zero)
                        throw new InvalidOperationException();
                    return m_buf;
                }
            }

            public byte[] ToArray()
            {
                byte[] result = new byte[this.Size];
                Array.Copy(this.Buffer, result, result.Length);
                return result;
            }

        }

        struct SecBufferS
        {
            public Int32 /*unsigned long*/ cbBuffer;             // Size of the buffer, in bytes
            public Int32 /*unsigned long*/ BufferType;           // Type of the buffer (below)
            public IntPtr /*void SEC_FAR **/ pvBuffer;            // Pointer to the buffer

            public void SilenceCompiler()
            {
                cbBuffer = 0;
                BufferType = 0;
                pvBuffer = IntPtr.Zero;
            }
        }

        struct SecBuffer
        {
            public Int32 /*unsigned long*/ cbBuffer;             // Size of the buffer, in bytes
            public Int32 /*unsigned long*/ BufferType;           // Type of the buffer (below)
            public byte[] /*void SEC_FAR **/ pvBuffer;            // Pointer to the buffer

            const int SECBUFFER_TOKEN = 2;

            public SecBuffer(byte[] buffer)
            {
                cbBuffer = buffer.Length;
                BufferType = SECBUFFER_TOKEN;
                pvBuffer = buffer;
            }
        }

        struct SECURITY_INTEGER // PTimeStamp
        {
            public Int32 /*unsigned long*/ LowPart;
            public Int32 /*long*/ HighPart;
        }

        enum SECURITY_STATUS
        {
            /// <summary>
            /// "The function completed successfully, but must be called again to complete the context"
            /// </summary>
            SEC_I_CONTINUE_NEEDED = 0x00090312,

            /// <summary>
            /// The function completed successfully, but CompleteToken must be called
            /// </summary>
            SEC_I_COMPLETE_NEEDED = 0x00090313,

            /// <summary>
            /// The function completed successfully, but both CompleteToken and this function must be called to complete the context
            /// </summary>
            SEC_I_COMPLETE_AND_CONTINUE = 0x00090314,
        }

        class SecPkgAttr
        {
            /// <summary>
            /// Security Context Attributes:
            /// </summary>
            internal enum Id : uint
            {
                Sizes = 0,
                Names = 1,
                Lifespan = 2,
                DceInfo = 3,
                StreamSizes = 4,
                KeyInfo = 5,
                Authority = 6,
                ProtoInfo = 7,
                PasswordExpiry = 8,
                SessionKey = 9,
                PackageInfo = 10,
                UserFlags = 11,
                NegotiationInfo = 12,
                NativeNames = 13,
                Flags = 14,
                UseValidated = 15,
                CredentialName = 16,
                TargetInformation = 17,
                AccessToken = 18,
                Target = 19,
                AuthenticationId = 20,
                LogoffTime = 21
            }

            internal struct SecPkgContext_NegotiationInfoW
            {
                public IntPtr/*PSecPkgInfoW*/ PackageInfo;
                public NegotiationState NegotiationState;
            }

            [System.Runtime.InteropServices.StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
            internal struct SecPkgInfoW
            {
                public SecPkgFlag fCapabilities;    // Capability bitmask
                public ushort wVersion;         // Version of driver
                public ushort wRPCID;           // ID for RPC Runtime
                public UInt32 cbMaxToken;       // Size of authentication token (max)
                public string Name;             // Text name
                public string Comment;          // Comment
            }

            internal struct SecPkgContext_Sizes
            {
                public uint cbMaxToken;
                public uint cbMaxSignature;
                public uint cbBlockSize;
                public uint cbSecurityTrailer;
            }

            internal enum NegotiationState : uint
            {
                Complete = 0,
                Optimistic = 1,
                InProgress = 2,
                Direct = 3,
                TryMulticred = 4,
            }

            /// <summary>
            /// Security Package Capabilities
            /// </summary>
            [Flags]
            internal enum SecPkgFlag : uint
            {
                Integrity = 0x00000001,  // Supports integrity on messages
                Privacy = 0x00000002,  // Supports privacy (confidentiality)
                TokenOnly = 0x00000004,  // Only security token needed
                Datagram = 0x00000008,  // Datagram RPC support
                Connection = 0x00000010,  // Connection oriented RPC support
                MultiRequired = 0x00000020,  // Full 3-leg required for re-auth.
                ClientOnly = 0x00000040,  // Server side functionality not available
                ExtendedError = 0x00000080,  // Supports extended error msgs
                Impersonation = 0x00000100,  // Supports impersonation
                AcceptWin32Name = 0x00000200,  // Accepts Win32 names
                Stream = 0x00000400,  // Supports stream semantics
                Negotiable = 0x00000800,  // Can be used by the negotiate package
                GssCompatible = 0x00001000,  // GSS Compatibility Available
                LogOn = 0x00002000,  // Supports common LsaLogonUser
                AsciiBuffers = 0x00004000,  // Token Buffers are in ASCII
                Fragment = 0x00008000,  // Package can fragment to fit
                MutualAuth = 0x00010000,  // Package can perform mutual authentication
                Delegation = 0x00020000,  // Package can delegate
                ReadonlyWithChecksum = 0x00040000,  // Package can delegate
            }

            [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
            internal struct SecPkgContext_KeyInfoW
            {
                public string sSignatureAlgorithmName;
                public string sEncryptAlgorithmName;
                public uint KeySize;
                public uint SignatureAlgorithm;
                public uint EncryptAlgorithm;
            }

        }//class2
    }
}
